﻿/**

 * Copyright (c) 2015-2017, FastDev 刘强 (fastdev@163.com).

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

using Mono.Cecil;
using Mono.Cecil.Cil;
using NLog;
using OF.ILModifier.Entity;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using OF.ILModifier.Extension;
namespace OF.ILModifier.Common
{
    public class ILHelper
    {
        public static TypeDefinition ToDefinition(Type self)
        {
            var module = ModuleDefinition.ReadModule(new MemoryStream(File.ReadAllBytes(self.Module.FullyQualifiedName)));
            return (TypeDefinition)module.LookupToken(self.MetadataToken);
        }

        public static MethodDefinition ToDefinition(MethodBase method)
        {
            var declaring_type = ToDefinition(method.DeclaringType);
            return (MethodDefinition)declaring_type.Module.LookupToken(method.MetadataToken);
        }

        public static bool IsCall(Instruction ins)
        {
            return (ins.OpCode.ToString() == "call" || ins.OpCode.ToString() == "callvirt");
        }

        public static bool IsParamsEqual(List<ParameterDefinition> paramL1,
            List<ParameterDefinition> paramL2)
        {
            if (paramL1.Count != paramL2.Count)
            {
                return false;
            }
            for (int pI = 0; pI < paramL1.Count; pI++)
            {
                var one = paramL1[pI];
                var other = paramL2[pI];
                if (one.ParameterType.GetFullName() != other.ParameterType.GetFullName())
                {
                    return false;
                }
            }
            return true;
        }

        public static bool HasReturnValue(Type type)
        {
            if (type == null || type.FullName == "System.Void")
            {
                return false;
            }
            return true;
        }

        public static void SearchInDll(string filePath, string keyWord)
        {
            if (!filePath.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)
                && !filePath.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
            {
                return;
            }

            Assembly ass = null;
            Type[] types = null;
            try
            {
                ass = Assembly.LoadFrom(filePath);
                types = ass.GetTypes();
            }
            catch (Exception ex)
            {
                Console.WriteLine("!!load failed:" + filePath);
                return;
            }
            var builder = new ILBuilder(filePath);
            builder.SetTargetTypeFilter(null)
                .SetTargetMethodFilter(null)
                .SetTargetInstructionFilter((type, method, ins) => {
                    if (ins.Operand != null)
                    {
                        string operand = string.Empty;
                        if (ins.Operand is MethodReference)
                        {
                            operand = ((MethodReference)ins.Operand).GetFullName();
                        }
                        else
                        {
                            operand = ins.Operand.ToString();
                        }
                        if (operand.IndexOf(keyWord) >= 0)
                        {
                            Console.WriteLine("Find method:" + method.ToString() + " at " + filePath + ",type:" + type + ", instruction is" + operand);
                        }
                    }
                    return false;
                })
                .SetBeforeInsFunc(null)
                .SetAfterInsFunc(null)
                .Visit();
        }

        public static void SearchDirCodeStrings(string rootDir, Func<string, bool> fileFilter,
         Func<object, bool> filterOperand, Action<string, MethodDefinition> findAction)
        {
            VisitPath(rootDir, (file) =>
            {
                if (file.Trim().EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)
                                   || file.Trim().EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
                {
                    if (fileFilter(file))
                    {
                        AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(file);
                        foreach (var module in asm.Modules)
                        {
                            foreach (var type in module.Types)
                            {
                                foreach (var method in type.Methods)
                                {
                                    if (method.Body == null || method.Body.Instructions == null)
                                    {
                                        continue;
                                    }
                                    foreach (var ins in method.Body.Instructions)
                                    {
                                        if (ins.OpCode == OpCodes.Ldstr && filterOperand(ins.Operand))
                                        {
                                            findAction(file, method);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            });
        }

        public static void VisitPath(string rootDir, Action<string> filePathVisitor)
        {
            foreach (var childFile in Directory.GetFiles(rootDir))
            {
                if (filePathVisitor != null)
                {
                    filePathVisitor(childFile);
                }
            }
            foreach (var dir in Directory.GetDirectories(rootDir))
            {
                VisitPath(dir, filePathVisitor);
            }
        }


    }
}

